package report;

import classifiers.CircleClassifier;
import histogram.HistogramDescr;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.LinkedList;

public class ReportGenerator {

    final int neiro[][];
    final int marked[][];
    int width;
    int height;
    long realSquare = 0;
    long realScale = 0;
    long imSize = 0;
    String reportName;
    private final long SCALE_SQUARE_COEFITIENT = (long) 1e12;
    private final long SCALE_LINE_COEFITIENT = (long) 1e6;
    private final int VISITED = 178;
    final boolean LIPID = false;
    final boolean MITAH = true;
    int[][] tempImage;
    int perimetr = 0;
    int square = 0;

    public ReportGenerator(final int[][] neiro, final int[][] marked, long realSquare, long realScale) {
        this.neiro = neiro;
        this.marked = marked;
        width = neiro.length;
        height = neiro[0].length;
        imSize = 1L * width * height;

        System.out.println("generate report");
        generateReport("report.csv");
        addToReport(realSquare, realScale);
        System.out.println("report is generated");
    }

    public void generateReport(String reportName) {
        this.reportName = reportName;
        File f = new File(reportName);
        if (f.exists()) {
            f.delete();
        }
        try {
            f.createNewFile();
        } catch (IOException ex) {
            System.out.println("Cannot create report file");
        }

    }

    public void addToReport(long realSquare, long realScale) {
        this.realScale = realScale;
        this.realSquare = realSquare;
        imSize = 1L * width * height;
        try {

            FileWriter sw = new FileWriter(reportName, true);
            BufferedWriter bw = new BufferedWriter(sw);

            countMitah(bw);
            countLipids(bw);

            bw.flush();

            sw.close();
            bw.close();
        } catch (Exception ex) {
            System.out.println(ex.getMessage());
        }

    }

    private void initImage(int[][] mas) {
        for (int i = 0; i < width; i++) {
            for (int j = 0; j < height; j++) {
                mas[i][j] = -1;
            }
        }
    }

    private void exployMitah(Point start, BufferedWriter bw, int organelSize) throws IOException {

        ArrayList<Integer> ovalsSquare = new ArrayList<Integer>();
        ArrayList<Integer> circlesSquare = new ArrayList<Integer>();
        ArrayList<Integer> ovalsPerimetr = new ArrayList<Integer>();
        ArrayList<Integer> circlesPerimetr = new ArrayList<Integer>();

        LinkedList<Integer> xQueue = new LinkedList<Integer>();
        LinkedList<Integer> yQueue = new LinkedList<Integer>();

        xQueue.push(start.getX());
        yQueue.push(start.getY());

        int dx[] = {-1, 1, 0, 0};
        int dy[] = {0, 0, -1, 1};

        while (!xQueue.isEmpty()) {
            int x = xQueue.pop();
            int y = yQueue.pop();

            if (x >= width || y >= height || x < 0 || y < 0) {
                continue;
            }

            boolean doNext = false;
            if (tempImage[x][y] != VISITED) {
                continue;
            }

            if (marked[x][y] == CircleClassifier.CIRCLES) {
                square = 0;
                perimetr = 0;
                changeType(x, y, VISITED,
                        CircleClassifier.CIRCLES);
                circlesSquare.add(square);
                circlesPerimetr.add(perimetr);
            } else if (marked[x][y] == CircleClassifier.OVAL) {
                square = 0;
                perimetr = 0;
                changeType(x, y, VISITED,
                        CircleClassifier.OVAL);
                ovalsSquare.add(square);
                ovalsPerimetr.add(perimetr);
            } else //            if (neiro[x][y] == HistogramDescr.BACK) 
            {
                tempImage[x][y] = HistogramDescr.BACK;
            }

            for (int i = 0; i < 4; i++) {
                xQueue.push(x + dx[i]);
                yQueue.push(y + dy[i]);
            }
        }

        bw.write("ovals;");
        bw.newLine();
        long sumSquare = 0;
        for (int i = 0; i < ovalsSquare.size(); i++) {
            sumSquare += ovalsSquare.get(i);
            bw.write((i + 1) + ";" + getRealSquare(ovalsSquare.get(i)) + ";"
                    + getFactorForm(ovalsSquare.get(i), ovalsPerimetr.get(i)) + ";");
            bw.newLine();
        }
        bw.write("volume density ovals;");
        bw.write(getVolumDensity(sumSquare, organelSize) + ";");
        bw.newLine();
        bw.write("amount density ovals;");
        bw.write(getAmountDensity(ovalsSquare.size(), sumSquare, MITAH, organelSize) + ";");
        bw.newLine();

        bw.write("circles;");
        bw.newLine();
        sumSquare = 0;
        for (int i = 0; i < circlesSquare.size(); i++) {
            sumSquare += circlesSquare.get(i);
            bw.write((i + 1) + ";" + getRealSquare(circlesSquare.get(i)) + ";"
                    + getFactorForm(circlesSquare.get(i), circlesPerimetr.get(i)) + ";");
            bw.newLine();
        }
        bw.write("volume density circles;");
        bw.write(getVolumDensity(sumSquare, organelSize) + ";");
        bw.newLine();
        bw.write("amount density circles;");
        bw.write(getAmountDensity(circlesSquare.size(), sumSquare, MITAH, organelSize) + ";");
        bw.newLine();
    }

    private String getRealSquare(int pixels) {
        double val = 1.0 * pixels / SCALE_SQUARE_COEFITIENT * realScale * realScale;
        String answ = val + "";
        return answ.format(answ, "%.2f").replace('.', ',');
    }

    private String getVolumDensity(long pixels, long organelSize) {
        double volum = pixels * 1.0 / organelSize;
        String tmp = volum + "";
        return tmp.format(tmp, "%.2f").replace('.', ',');
    }

    private String getFactorForm(long sq, long perim) {
        if (perim == 0) {
            return "xz";
        }
        double factor = 4 * Math.PI * sq / perim / perim;
        String tmp = factor + "";
        return tmp.format(tmp, "%.2f").replace('.', ',');
    }

    private String getAmountDensity(int n, long sum, boolean ORGAN_TYPE, long organelSize) {
        if (sum == 0) {
            return "xz";
        }
        final double betaMitah = 2.25;
        final double betaLipid = 1.38;
        double volum = sum * 1.0 / organelSize;
        double curBeta = ORGAN_TYPE == LIPID ? betaLipid : betaMitah;
        double val = Math.pow(n * 1.0 / sum, 1.5) / curBeta / Math.sqrt(volum);
        String tmp = val + "";
        return tmp.format(tmp, "%.2f").replace('.', ',');
    }

    private void countMitah(BufferedWriter bw) throws IOException {
        bw.write("amount of mitahondrias;");
        bw.newLine();
        tempImage = new int[width][height];
        initImage(tempImage);
        int mitahondries = 1;
        ArrayList<Point> mitahStart = new ArrayList<Point>();
        ArrayList<Integer> mitahSize = new ArrayList<Integer>();
        long sumSquare = 0;
        for (int i = 0; i < width; i++) {
            for (int j = 0; j < height; j++) {
                if (tempImage[i][j] == -1 && neiro[i][j] == HistogramDescr.MITAH) {
                    mitahStart.add(new Point(i, j));
                    square = 0;
                    perimetr = 0;
                    findSquare(i, j, HistogramDescr.MITAH);
                    sumSquare += square;
                    bw.write(mitahondries + ";" + getRealSquare(square) + ";" + getFactorForm(square, perimetr) + ";");
                    bw.newLine();
                    mitahSize.add(square);
                    mitahondries++;
                }
            }
        }
        bw.write("volume density;");
        bw.write(getVolumDensity(sumSquare, imSize) + ";");
        bw.newLine();
        bw.write("amount density;");
        bw.write(getAmountDensity(mitahStart.size(), sumSquare, MITAH, imSize) + ";");
        bw.newLine();

        for (int k = 0; k < mitahStart.size(); k++) {
            bw.write("mitah " + (k + 1) + ";");
            exployMitah(mitahStart.get(k), bw, mitahSize.get(k));
        }
    }

    private void countLipids(BufferedWriter bw) throws IOException {
        bw.write("amount of lipids;");
        bw.newLine();
        tempImage = new int[width][height];
        initImage(tempImage);
        int lipids = 1;
        ArrayList<Point> lipidStart = new ArrayList<Point>();
        long sumSquare = 0;
        for (int i = 0; i < width; i++) {
            for (int j = 0; j < height; j++) {
                if (tempImage[i][j] == -1 && neiro[i][j] == HistogramDescr.LIPID) {
                    lipidStart.add(new Point(i, j));
                    square = 0;
                    perimetr = 0;
                    findSquare(i, j, HistogramDescr.LIPID);
                    sumSquare += square;
                    bw.write(lipids + ";" + getRealSquare(square) + ";" + getFactorForm(square, perimetr) + ";");
                    bw.newLine();
                    lipids++;
                }
            }
        }
        bw.write("volume density;");
        bw.write(getVolumDensity(sumSquare, imSize) + ";");
        bw.newLine();
        bw.write("amount density;");
        bw.write(getAmountDensity(lipidStart.size(), sumSquare, LIPID, imSize) + ";");
        bw.newLine();
    }

    private void findSquare(int xPoint, int yPoint, int... inComponent) {

        LinkedList<Integer> xQueue = new LinkedList<Integer>();
        LinkedList<Integer> yQueue = new LinkedList<Integer>();

        xQueue.push(xPoint);
        yQueue.push(yPoint);

        int dx[] = {-1, 1, 0, 0};
        int dy[] = {0, 0, -1, 1};

        while (!xQueue.isEmpty()) {
            int x = xQueue.pop();
            int y = yQueue.pop();

            if (x >= width || y >= height || x < 0 || y < 0) {
                continue;
            }

            boolean doNext = false;

            for (int p : inComponent) {
                if (neiro[x][y] == p) {
                    doNext = true;
                }
            }

            if (!doNext || tempImage[x][y] == VISITED) {
                continue;
            }

            if (x == 0 || y == 0 || x == width - 1 || y == height - 1) {
                perimetr++;
            } else {
                boolean flag = false;
                for (int i = 0; i < 4; i++) {
                    if (neiro[x + dx[i]][y + dy[i]] != HistogramDescr.MITAH) {
                        flag = true;
                    }
                }
                if (flag) {
                    perimetr++;
                }
            }

            tempImage[x][y] = VISITED;

            square++;
            for (int i = 0; i < 4; i++) {
                xQueue.push(x + dx[i]);
                yQueue.push(y + dy[i]);
            }
        }

    }

    private void changeType(int xPoint, int yPoint, int OLD_TYPE, int NEW_TYPE) {

        square = 0;
        perimetr = 0;
        LinkedList<Integer> xQueue = new LinkedList<Integer>();
        LinkedList<Integer> yQueue = new LinkedList<Integer>();

        xQueue.push(xPoint);
        yQueue.push(yPoint);

        int dx[] = {-1, 1, 0, 0};
        int dy[] = {0, 0, -1, 1};

        while (!xQueue.isEmpty()) {
            int x = xQueue.pop();
            int y = yQueue.pop();

            if (x >= width || y >= height || x < 0 || y < 0) {
                continue;
            }

            if (tempImage[x][y] != VISITED || marked[x][y] != NEW_TYPE) {
                continue;
            }

            tempImage[x][y] = NEW_TYPE;
            if (x == 0 || y == 0 || x == width - 1 || y == height - 1) {
                perimetr++;
            } else {
                boolean flag = false;
                for (int i = 0; i < 4; i++) {
                    if (neiro[x + dx[i]][y + dy[i]] == HistogramDescr.BACK
                            || neiro[x + dx[i]][y + dy[i]] == HistogramDescr.EDGE
                            || marked[x + dx[i]][y + dy[i]] != NEW_TYPE) {
                        flag = true;
                    }
                }
                if (flag) {
                    perimetr++;
                }
            }

            square++;
            for (int i = 0; i < 4; i++) {
                xQueue.push(x + dx[i]);
                yQueue.push(y + dy[i]);
            }
        }
    }

    private class Point {

        int x;
        int y;

        public Point(int x, int y) {
            this.x = x;
            this.y = y;
        }

        public int getX() {
            return x;
        }

        public int getY() {
            return y;
        }
    }
}
